from requests.sessions import Session
from urllib.parse import urlparse, urlunparse
from bs4 import BeautifulSoup
import inspect
from pprint import pprint


class WithSession(object):
    """
    用作API类的基类，便于多个API请求共用同一个session
    """

    def __init__(self):
        self._session = None

    def __enter__(self):
        # print('enter')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # print('exit')
        if self.session is not None:
            self.session.close()
            self.session = None

    def use_session(self, s):
        self._session = s
        return self

    @property
    def session(self):
        return self._session

    @session.setter
    def session(self, s):
        self._session = s
        return self


class RestRequest(object):
    """
    Rest 请求封装装饰器
    """

    # 类变量REST的请求的基础URL
    BASE_URL = 'http://localhost:18080/rest/'

    def __init__(self, url='', method='get', **kw):
        """
        获取装饰器参数
        :param url: REST请求资源
        :param method: REST请求地址
        :param kw:
        """
        # print('init rest request %s' % self.BASE_URL)
        self._url = url
        self._method = method
        self._args = kw

    def __call__(self, rest_fun):
        """
        执行装饰器
        :param rest_fun: 装饰器装饰的函数
        :return:
        """
        self._func = rest_fun
        self._is_class = False
        try:
            if inspect.getfullargspec(rest_fun).args[0] == 'self':
                self._is_class = True
        except IndexError:
            pass

        def wrapper(*args, **kwargs):
            # print('run wrapper', rest_fun)
            self._owner = args[0] if self._is_class else None

            ret = self._func(*args, **kwargs) or {}
            url = self.create_url(ret)
            args = {}
            args.update(self._args)
            args.update(ret)

            return Rest(self._method,
                        url,
                        self.create_session(),
                        args)
        # print('return wrapper', rest_fun)
        return wrapper

    def create_url(self, ret):
        """
         合成REST API的URL
        :return:
        """
        url = self._url
        if 'url_params' in ret:
            url_params = ret['url_params']
            ret.pop('url_params')
            url = self._url.format(**url_params)
        if url.startswith('/'):
            parsed_url_list = list(urlparse(self.BASE_URL))
            parsed_url_list[2] = url
            return urlunparse(parsed_url_list)
        else:
            return self.BASE_URL + url

    def create_session(self):
        """
        生成REST请求的SESSION
        :return:
        """
        if self._is_class and isinstance(self._owner, WithSession):
            if self._owner.session is not None:
                return self._owner.session
        print('create a new session')
        return Session()


def context(func):
    def wrapper(self):
        self._request()
        try:
            res = func(self)
        finally:
            self._log()
        return res
    return wrapper


class Rest(object):
    """
    REST 请求对象
    """

    def __init__(self, method, url, session, args):
        self._method = method
        self._url = url
        self._session = session
        self._args = args
        self._response = None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._session is not None:
            self._session.close()

    @context
    def to_json(self):
        assert self._response is not None, 'response is none'
        try:
            response_json = self._response.json()
            return response_json
        except ValueError as e:
            pprint(self._response)
            raise e

    @context
    def to_content(self):
        assert self._response is not None, 'response is none'
        response_content = self._response.content
        return response_content

    @context
    def to_html(self):
        assert self._response is not None, 'response is none'
        response_html = BeautifulSoup(self._response.content, 'html.parser')
        return response_html

    @context
    def get_response(self):
        return self._response

    @property
    def html(self):
        return self.to_html()

    @property
    def json(self):
        return self.to_json()

    @property
    def content(self):
        return self.to_content()

    @property
    def session(self):
        return self._session

    @property
    def response(self):
        return self.get_response()

    def _request(self):
        if not self._response:
            print("%s %s with args:" % (self._method, self._url))
            pprint(self._args)
            self._response = self._session.request(self._method, self._url, **self._args)
        pass

    def _log(self):
        pass

rest = RestRequest


class User(object):
    """
    用户管理封装
    """

    def __init__(self, user_api, username, password, login_verify=None, logout_verify=None):
        """
        初始化用户
        :param user_api: 用户相关的API，必须实现login, logout方法
        :param username: 用户名
        :param password: 密码
        :param login_verify: 登录验证方法
        :param logout_verify: 登出验证方法
        """
        assert issubclass(user_api, WithSession)
        self.user_api = user_api()
        self._username = username
        self._password = password

        self._login_verify = login_verify
        if login_verify is None:
            def default_login_verify(r):
                return r.json['ok']
            self._login_verify = default_login_verify

        self._logout_verify = logout_verify
        if logout_verify is None:
            def default_logout_verify(r):
                return r.json['ok']
            self._logout_verify = default_logout_verify

        self._session = None
        self._api_dict = {user_api.__name__: self.user_api}

    def __enter__(self):
        assert hasattr(self.user_api, 'login'), "No login Api found in {}".format(self.user_api)
        login = self.user_api.login(self._username, self._password)
        assert self._login_verify(login), "User {} login verify fail with password {}".format(self._username,
                                                                                              self._password)
        self._session = login.session
        self.user_api.session = self._session
        return self

    def get_api(self, api_class):
        assert issubclass(api_class, WithSession)
        api_class_name = api_class.__name__
        # print("get api in ", api_class_name)
        if api_class_name in self._api_dict:
            api = self._api_dict[api_class_name]
        else:
            api = api_class()
            api.session = self._session
            self._api_dict[api_class.__name__] = api
        return api

    def __exit__(self, exc_type, exc_val, exc_tb):
        assert self._session is not None
        try:
            if hasattr(self.user_api, 'logout'):
                logout = self.user_api.logout()
                assert self._logout_verify(logout), "Logout verify fail"
        finally:
            self._session.close()
        pass

if __name__ == '__main__':
    rest.BASE_URL = "http://127.0.0.1:18080/rest/"
    rest1 = rest()
    rest2 = rest()
    print(rest1.BASE_URL)
    print(rest2.BASE_URL)
